use darling::FromField;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{spanned::Spanned, Data, DataStruct, DeriveInput, Fields, FieldsNamed};

pub struct BuilderContext {
    fields: Vec<Field>,
}
impl BuilderContext {
    pub fn new(input: DeriveInput) -> syn::Result<Self> {
        let input_span = input.span();
        let fds = if let Data::Struct(DataStruct {
            fields: Fields::Named(FieldsNamed { named, .. }),
            ..
        }) = input.data
        {
            named
        } else {
            return Err(syn::Error::new_spanned(
                &input,
                "DbAccess宏只支持结构体类型".to_string(),
            ));
        };
        let mut fields: Vec<Field> = Vec::new();
        for f in fds.into_iter() {
            let fargs = FieldArgs::from_field(&f).unwrap_or_default();
            let dict = if let Some(d) = fargs.dict {
                let ds: Vec<&str> = d.split('.').collect();
                if ds.len() != 2 {
                    return Err(syn::Error::new_spanned(
                        &f,
                        "dict定义错误，必须是entity.fieldname的形式".to_string(),
                    ));
                }
                Some((ds[0].to_owned(), ds[1].to_owned()))
            } else {
                None
            };
            let span = f.span();
            let (ty2, is_null) = get_field_type(&f)?;
            fields.push(Field {
                name: f.ident.unwrap(),
                ty: f.ty,
                dict,
                primary_key: fargs.primary_key,
                span: span,
                is_null,
                ty2,
            });
        }
        let pk_count = fields.iter().filter(|f| f.primary_key).count();
        if pk_count == 0 {
            return Err(syn::Error::new(
                input_span,
                "Entity实体没有主键".to_string(),
            ));
        } else if pk_count > 1 {
            return Err(syn::Error::new(
                input_span,
                "Entity实体仅支持单主键".to_string(),
            ));
        }
        Ok(BuilderContext {
            fields,
        })
    }
}

impl BuilderContext {
    pub fn generate(&self) -> TokenStream {
        let base_method = self.gen_base_method();
        let find_in_page_method = self.gen_find_in_page_method();
        quote! {
            use sea_orm::{
                QueryOrder, QuerySelect, QueryTrait, TransactionTrait,
            };
            use futures::future::FutureExt;
            pub struct DataAccess;
            #[async_trait::async_trait]
            impl shq_common::prelude::DataAccessTrait for DataAccess {
                #base_method
                #find_in_page_method
            }
            #[static_init::constructor]
            extern "C" fn init_dao() {
                let entity=Entity;
                shq_common::prelude::set_dao(entity.table_name(), &DataAccess);
            }
        }
    }
    fn gen_find_in_page_method(&self) -> TokenStream {
        let mut fk_sql_exprs: Vec<TokenStream> = Vec::new();
        for f in self.fields.iter() {
            if f.dict.is_none() {
                continue;
            }
            let (entity_name, name_col) = f.dict.as_ref().unwrap();
            let col_alias = format!("_{}__{}", entity_name, name_col);
            let ename = Ident::new(
                format!("{}", heck::AsPascalCase(entity_name)).as_str(),
                f.span,
            );
            let col_name_ident = Ident::new(
                format!("{}", heck::AsPascalCase(name_col)).as_str(),
                f.span,
            );
            let entity_ident = Ident::new(entity_name, f.span);
            fk_sql_exprs.push(quote!{
                .column_as(Expr::col((super::#entity_ident::Entity, super::#entity_ident::Column::#col_name_ident)), #col_alias)
                .join_as(
                    sea_orm::JoinType::LeftJoin,
                    Relation::#ename.def(),
                    sea_orm::sea_query::Alias::new(#entity_name),
                )
            });
        }
        quote! {
            async fn find_in_page(
                &self,
                where_causes: Option<shq_common::prelude::WhereCauses>,
                orders: Option<shq_common::prelude::OrderItems>,
                page: u64,
                page_size: u64,
                tx: Option<&sea_orm::DatabaseTransaction>,
            ) -> shq_common::prelude::Result<(Vec<serde_json::Value>, u64)> {
                if page == 0 {
                    return Err(shq_common::prelude::Error::from("参数page从1开始"));
                }
                let pk_col = Expr::col((Entity, Column::Id));
                let sel_sql = Entity::find()
                    #(#fk_sql_exprs)*
                    .apply_if(where_causes, |query, v| {
                        let wcs = v.parse(Entity);
                        query.filter(wcs)
                    })
                    .apply_if(orders, |query, v| {
                        let ors = v.parse(Entity);
                        ors.into_iter()
                            .fold(query, |acc, (col, ord, _)| acc.order_by(col, ord.clone()))
                    })
                    .order_by_desc(pk_col)
                    .into_json();
                let datas=if let Some(tx) = tx {
                    let pages=sel_sql.paginate(tx,page_size);
                    let num = pages.num_items().await?;
                    let jsons  = pages.fetch_page(page - 1).await?;
                    (jsons, num)
                }else{
                    let pages=sel_sql.paginate(crate::get_db_conn(),page_size);
                    let num = pages.num_items().await?;
                    let jsons  = pages.fetch_page(page - 1).await?;
                    (jsons, num)
                };
                Ok(datas)
            }
        }
    }
    fn gen_base_method(&self) -> TokenStream {
        let pks: Vec<&Field> = self.fields.iter().filter(|f| f.primary_key).collect();
        let ty = &pks[0].ty;
        let pk_name=&pks[0].name;
        let pk_name_str=pk_name.to_string();
        let field_props = self.fields.iter().map(|f| {
            let is_pk = f.primary_key;
            let is_null = f.is_null;
            let ty2 = &f.ty2;
            let fname=&f.name.to_string();
            let dict = match &f.dict {
                Some((e, c)) => quote!(Some((#e,#c))),
                None => quote!(None),
            };
            quote! {
                shq_common::prelude::FieldProps {
                    name: #fname,
                    field_type: shq_common::prelude::FieldType::#ty2,
                    is_pk: #is_pk,
                    is_null: #is_null,
                    dict: #dict,
                }
            }
        });
        quote! {
            async fn create_by_json(&self, map_val: serde_json::Value,tx: Option<&sea_orm::DatabaseTransaction>,) -> shq_common::prelude::Result<u64> {
                let obj = ActiveModel::from_json(map_val)?;
                let ins_sql=Entity::insert(obj);
                let res=if let Some(tx) = tx {
                    ins_sql.exec(tx).await?
                }else{
                    ins_sql.exec(crate::get_db_conn()).await?
                };
                let pid = res.last_insert_id as u64;
                Ok(pid)
            }
            async fn update_by_json(&self, map_val: serde_json::Value,tx: Option<&sea_orm::DatabaseTransaction>,) -> shq_common::prelude::Result<u64> {
                let obj = ActiveModel::from_json(map_val)?;
                let upd_sql = Entity::update(obj);
                let res=if let Some(tx) = tx {
                    upd_sql.exec(tx).await?
                }else{
                    upd_sql.exec(crate::get_db_conn()).await?
                };
                let pid = res.id as u64;
                Ok(pid)
            }
            async fn save_by_json(&self, map_val: serde_json::Value,tx: Option<&sea_orm::DatabaseTransaction>,) -> shq_common::prelude::Result<u64> {
                let obj = ActiveModel::from_json(map_val)?;
                let mut res=if let Some(tx) = tx {
                    obj.save(tx).await?
                }else{
                    obj.save(crate::get_db_conn()).await?
                };
                let pid = res.id.take().unwrap_or(0);
                Ok(pid as u64)
            }
            async fn delete_by_json(&self, map_val: serde_json::Value,tx: Option<&sea_orm::DatabaseTransaction>,) -> shq_common::prelude::Result<u64> {
                let obj = ActiveModel::from_json(map_val)?;
                let res=if let Some(tx) = tx {
                    obj.delete(tx).await?
                }else{
                    obj.delete(crate::get_db_conn()).await?
                };
                Ok(res.rows_affected)
            }
            async fn delete_by_id(&self, id: u64,tx: Option<&sea_orm::DatabaseTransaction>,) -> shq_common::prelude::Result<u64> {
                let find_sql = Entity::find_by_id(id as #ty);
                let obj_opt=if let Some(tx) = tx {
                    find_sql.one(tx).await?
                }else{
                    find_sql.one(crate::get_db_conn()).await?
                };
                if let Some(obj) = obj_opt {
                    let res=if let Some(tx) = tx {
                        obj.delete(tx).await?
                    }else{
                        obj.delete(crate::get_db_conn()).await?
                    };
                    Ok(res.rows_affected)
                } else {
                    Ok(0)
                }
            }
            async fn delete_by_ids(&self, ids: Vec<u64>,tx: Option<&sea_orm::DatabaseTransaction>,) -> shq_common::prelude::Result<u64> {
                let del_sql = Entity::delete_many()
                    .filter(Column::Id.is_in(ids));
                let res=if let Some(tx) = tx {
                    del_sql.exec(tx).await?
                }else{
                    del_sql.exec(crate::get_db_conn()).await?
                };
                Ok(res.rows_affected)
            }
            async fn delete_by_map(&self, para_vals: &std::collections::HashMap<String,String>,tx: Option<&sea_orm::DatabaseTransaction>,) -> shq_common::prelude::Result<u64> {
                let del = para_vals
                    .iter()
                    .fold(Entity::delete_many(), |acc, (k, v)| {
                        acc.filter(Expr::col(sea_orm::sea_query::Alias::new(k)).eq(v))
                    });
                let res=if let Some(tx) = tx {
                    del.exec(tx).await?
                }else{
                    del.exec(crate::get_db_conn()).await?
                };
                Ok(res.rows_affected)
            }
            async fn find_by_id(&self, id: u64,tx: Option<&sea_orm::DatabaseTransaction>,) -> shq_common::prelude::Result<Option<serde_json::Value>> {
                let find_sql = Entity::find_by_id(id as #ty);
                let obj_opt=if let Some(tx) = tx {
                    find_sql.one(tx).await?
                }else{
                    find_sql.one(crate::get_db_conn()).await?
                };
                if let Some(obj) = obj_opt {
                    let json = serde_json::to_value(obj).unwrap();
                    return Ok(Some(json));
                } else {
                    Ok(None)
                }
            }
            fn get_field_props(&self) -> Vec<shq_common::prelude::FieldProps>{
                let fprops:Vec<shq_common::prelude::FieldProps>=vec![
                    #(#field_props),*
                ];
                fprops
            }
            fn get_primary_key_name(&self) -> &'static str{
                #pk_name_str
            }
            async fn find_json_by_uniqe_keys(&self, para_vals: std::collections::HashMap<String,String>,tx: Option<&sea_orm::DatabaseTransaction>,) -> shq_common::prelude::Result<Option<serde_json::Value>> {
                let sel=para_vals.into_iter().fold(Entity::find(), |acc,(k,v)|{
                    acc.filter(Expr::col((Entity,sea_orm::sea_query::Alias::new(k))).eq(v))
                });
                let obj_opt=if let Some(tx) = tx {
                    sel.into_json().one(tx).await?
                }else{
                    sel.into_json().one(crate::get_db_conn()).await?
                };
                Ok(obj_opt)
            }
            async fn find_id_by_uniqe_keys(&self, para_vals: std::collections::HashMap<String,String>,tx: Option<&sea_orm::DatabaseTransaction>,) -> shq_common::prelude::Result<Option<u64>> {
                let sel=para_vals.into_iter().fold(Entity::find(), |acc,(k,v)|{
                    acc.filter(Expr::col((Entity,sea_orm::sea_query::Alias::new(k))).eq(v))
                });
                let obj_opt=if let Some(tx) = tx {
                    sel.one(tx).await?
                }else{
                    sel.one(crate::get_db_conn()).await?
                };
                match obj_opt {
                    Some(v) => {
                        let id=v.#pk_name as u64;
                        return Ok(Some(id));
                    },
                    None => Ok(None),
                }
            }
        }
    }
}
fn get_generic_inner_type<'a>(ty: &'a syn::Type, outer_ident_name: &str) -> Option<&'a syn::Type> {
    if let syn::Type::Path(syn::TypePath { ref path, .. }) = ty {
        // 这里我们取segments的最后一节来判断是不是`T<U>`，这样如果用户写的是`foo:bar::T<U>`我们也能识别出最后的`T<U>`
        if let Some(seg) = path.segments.last() {
            if seg.ident == outer_ident_name {
                if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
                    ref args,
                    ..
                }) = seg.arguments
                {
                    if let Some(syn::GenericArgument::Type(inner_ty)) = args.first() {
                        return Some(inner_ty);
                    }
                }
            }
        }
    }
    None
}
fn get_field_type(f: &syn::Field) -> syn::Result<(Ident, bool)> {
    let inner_ty = get_generic_inner_type(&f.ty, "Option");
    let (ty, is_null) = if let Some(t) = inner_ty {
        (t, true)
    } else {
        (&f.ty, false)
    };
    let convert_ty = |ty_str: &str| {
        let ty_str_new = match ty_str {
            "String" | "Json" => Ok("String"),
            "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => Ok("Int"),
            | "f32" | "f64" => Ok("Float"),
            "bool" => Ok("Bool"),
            _ => {
                if ty_str.ends_with("ChronoDate") {
                    Ok("Date")
                } else if ty_str.ends_with("ChronoDateTime") {
                    Ok("Datetime")
                } else {
                    Err(syn::Error::new(
                        f.span(),
                        format!("不支持的类型Verbatim:{}", ty_str),
                    ))
                }
            }
        };
        ty_str_new.map(|n| Ident::new(n, f.span()))
    };
    let res = match ty {
        syn::Type::Verbatim(v) => {
            let ty_str = v.to_string();
            convert_ty(ty_str.as_str())
        }
        syn::Type::Path(v) => {
            if let Some(ps) = v.path.segments.first() {
                let ty_str = ps.ident.to_string();
                convert_ty(ty_str.as_str())
            } else {
                Err(syn::Error::new(f.span(), format!("类型{:?}异常", v)))
            }
        }
        _ => Err(syn::Error::new(f.span(), format!("不支持的类型{:?}", ty))),
    };
    res.map(|v| (v, is_null))
}
pub struct Field {
    name: Ident,
    ty: syn::Type,
    dict: Option<(String, String)>,
    primary_key: bool,
    is_null: bool,
    span: Span,
    ty2: Ident,
}

#[derive(Debug, Default, FromField)]
#[darling(default, attributes(db_access, sea_orm))]
struct FieldArgs {
    dict: Option<String>,
    primary_key: bool,
}
